package com.onpositive.mediawiki.crawler;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import net.sourceforge.jwbf.core.actions.Get;
import net.sourceforge.jwbf.core.actions.util.HttpAction;
import net.sourceforge.jwbf.mediawiki.actions.MediaWiki;
import net.sourceforge.jwbf.mediawiki.actions.queries.TitleQuery;
import net.sourceforge.jwbf.mediawiki.actions.util.VersionException;
import net.sourceforge.jwbf.mediawiki.bots.MediaWikiBot;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

public class AllCategoriesQuery extends TitleQuery<String>
{
	/** Category starting markup */
	protected static final String CATEGORY_STARTING_MARKUP = "<c ";
	/*	*//** Span closing markup */
	/*
	 * protected static final String SPAN_CLOSE_MARKUP = "</span>";
	 */
	/** Pattern to parse returned page, @see {@link #parseHasMore(String)}. */
	protected static final Pattern HAS_MORE_PATTERN = Pattern.compile("<query-continue>.*?<allcategories *acfrom=\"([^\"]*)\" */>.*?</query-continue>", Pattern.DOTALL | Pattern.MULTILINE);
	protected String filterPrefix; 

	/**
	 * Constructs new query instance
	 * @param bot {@link MediaWikiBot} for performing query
	 * @param preffix filtering preffix resulting categories should start with. 
	 * in case of getting all categories, should be null
	 * if prefix != null, query will return only <=500 results 
	 * @throws VersionException if query is not supported
	 */
	protected AllCategoriesQuery(MediaWikiBot bot, String preffix) throws VersionException
	{
		super(bot);
		this.filterPrefix = preffix;
	}

	@Override
	protected HttpAction prepareCollection()
	{
		String uS = generateRequest(getNextPageInfo());
		return new Get(uS);
	}

	protected String generateRequest(String from)
	{
		String result = "/api.php?action=query&list=allcategories"

		+ ((from != null && from.length() > 0) ? ("&acfrom=" + MediaWiki.encode(from.replaceAll("&amp;", "&"))) : "")
		+ ((filterPrefix!= null && filterPrefix.length() > 0) ? ("&acprefix=" + MediaWiki.encode(filterPrefix.replaceAll("&amp;", "&"))) : "")
		+ "&aclimit=5000&acprop=size&format=xml";
		return result;
	}

	@Override
	protected Collection<String> parseArticleTitles(String s)
	{
		ArrayList<String> resultList = new ArrayList<String>();
		try
		{
			Document doc = load(s);
			Element root = doc.getDocumentElement();			
			XPathFactory factory = XPathFactory.newInstance();
			XPath xPath = factory.newXPath();
			Element categoryContainer = (Element) xPath.evaluate("//query/allcategories", root, XPathConstants.NODE);
			NodeList list = categoryContainer.getChildNodes();
			for (int i = 0; i < list.getLength(); i++)
			{
				Node item = list.item(i);
				if (item instanceof Element)
				{
					if (isNotEmpty((Element) item))
					{
						resultList.add(item.getTextContent());
					}
				}
			}
			return resultList;
		} catch (Exception e)
		{
			e.printStackTrace();
		}

	/*	int idx = s.indexOf(CATEGORY_STARTING_MARKUP);
		int nameStart = s.indexOf('>', idx); // Search tag end
		while (idx > -1)
		{
			// Firstly, let's skip </span> after "<c>" markup and possible
			// whitespaces
			int i = nameStart + 1;
			// Will consider a text before next tag start ('<' char) a category
			// name
			int endIdx = s.indexOf('<', i);
			if (endIdx > -1)
			{
				String categoryName = s.substring(i, endIdx).trim();
				if (categoryName.length() > 0)
					resultList.add(categoryName);
				idx = s.indexOf(CATEGORY_STARTING_MARKUP, i);
				nameStart = s.indexOf('>', idx);
			} else
				idx = -1;
		}*/

		return resultList;
	}

	protected boolean isNotEmpty(Element item)
	{
		if (!item.hasAttribute("size") || !item.getAttribute("size").contentEquals("0"))
			return true;
		return false;
	}

	/**
	 * Gets the information about a follow-up page from a provided api response.
	 * If there is one, a new request is added to msgs by calling
	 * generateRequest. If no exists, the string is empty.
	 * 
	 * @param s
	 *            text for parsing
	 * @return the
	 */
	protected String parseHasMore(final String s)
	{
		if (filterPrefix != null)
			return "";
		Matcher m = HAS_MORE_PATTERN.matcher(s);
		if (m.find())
		{
			return m.group(1);
		} else
		{
			return "";
		}
	}

	public static Document load(String xml) throws Exception
	{
		DocumentBuilder builder = getDocumentBuilder();
		Document document = builder.parse(new InputSource(new StringReader(xml)));
		return document;
	}

	public static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException
	{
		DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
		builderFactory.setNamespaceAware(true);
		DocumentBuilder builder = builderFactory.newDocumentBuilder();
		return builder;
	}

}
